#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>

#include <utils.h>

#include <vector>
#include <string>

bool g_verbose = false;

bool g_debug = false;

std::string g_root;

LuaWrap g_type_predicate_script;

static int _fmt_buffer_size = 0;

static char *_fmt_buffer = NULL;

int message(int level, const char *fmt, ...) {
    int      n;
    va_list  ap;
    char    *np;

    if (_fmt_buffer == NULL) {
        _fmt_buffer_size = 4096;
        _fmt_buffer = (char *) malloc (_fmt_buffer_size);

        if (_fmt_buffer == NULL)
            return -1;
    }

    for (;;) {
        va_start(ap, fmt);
        n = vsnprintf(_fmt_buffer, _fmt_buffer_size, fmt, ap);
        va_end(ap);

        if (n > -1 && n < _fmt_buffer_size)
            break;

        _fmt_buffer_size *= 2;
        if ((np = (char *) realloc (_fmt_buffer, _fmt_buffer_size)) == NULL) {
            free(_fmt_buffer);
            _fmt_buffer = NULL;
            return -1;
        }
        else {
            _fmt_buffer = np;
        }
    }

    switch (level) {
    case MSG_LEVEL_MESSAGE:
        fputs("MSG: ", stdout);
        break;
    case MSG_LEVEL_WARNING:
        fputs("WAN: ", stdout);
        break;
    case MSG_LEVEL_ERROR:
        fputs("ERR: ", stdout);
        break;
    case MSG_LEVEL_DEBUG:
        fputs("DBG: ", stdout);
        break;
    default:
        fputs("UNKNOW", stdout);
        break;
    }

    fputs(_fmt_buffer, stdout);

    return n;
}

int setup_root()
{
    char *p = getenv("ytags_root");

    if (p)
        g_root = p;
    else {
        std::string cwd;
        if (get_cwd(cwd) < 0)
            return -1;
        g_root = cwd;
    }
    return 0;
}

int setup_type_predicate_script()
{
    if (g_root.empty()) {
        message(MSG_LEVEL_ERROR, "root not setup\n");
        return -1;
    }

    std::string filename = g_root;
    filename.push_back(FS_SEP);
    filename += "scripts/type-p.lua";

    if (g_type_predicate_script.load(filename.c_str()) == false)
        return -1;

    if (g_type_predicate_script.run() == false)
        return -1;

    return 0;
}

#define CHAR_IS_ALPHA(c)                        \
    ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))

int adjust_path(const char   *path,
                std::string&  result)
{
    std::vector<std::string> splits;
    std::string              str;
    char                     c;

    // the root part
    std::string              root;
    {
#ifdef WIN32
        if (strlen(path) >= 2) {
            c = *path;
            if (CHAR_IS_ALPHA(c) && path[1] == ':') {
                if (path[2] == FS_SEP) {
                    root.assign(path, 3);
                    path += 3;
                }
                else {
                    root.assign(path, 2);
                    path += 2;
                }
            }
        }
#else
        if (path[0] == FS_SEP) {
            root.push_back(FS_SEP);
            ++path;
        }
#endif
    }

    for (;;) {
        c = *path++;

        if (c == FS_SEP || c == 0) {
            if (str.size() > 0) {
                // check . and ..
                if (str.size() == 2 && str[0] == '.' && str[1] == '.') {
                    if (splits.size() > 0)
                        splits.pop_back();
                    else
                        return -1;
                }
                else if (!(str.size() == 1 && str[0] == '.')) {
                    splits.push_back(str);
                }

                str.clear();
            }
        }
        else
            str.push_back(c);

        if (c == 0)
            break;
    }

    result = root;

    int i;
    int nsplits = splits.size();

    for (i = 0; i < nsplits; i++) {
        result += splits[i];

        if (i + 1 < nsplits)
            result.push_back(FS_SEP);
    }

    return 0;
}
